<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"><meta name="description" content="Rust中的Rc、Arc和Box::leak()机制"><meta name="keywords" content="Rust"><meta name="author" content="LiYang"><meta name="copyright" content="LiYang"><title>Rust中的Rc、Arc和Box::leak()机制 | 一条鲤鱼</title><link rel="shortcut icon" href="/melody-favicon.ico"><link rel="stylesheet" href="/css/index.css?version=1.9.0"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/font-awesome@latest/css/font-awesome.min.css?version=1.9.0"><meta name="format-detection" content="telephone=no"><meta http-equiv="x-dns-prefetch-control" content="on"><link rel="dns-prefetch" href="https://cdn.jsdelivr.net"><meta http-equiv="Cache-Control" content="no-transform"><meta http-equiv="Cache-Control" content="no-siteapp"><script>var GLOBAL_CONFIG = { 
  root: '/',
  algolia: undefined,
  localSearch: undefined,
  copy: {
    success: 'Copy successfully',
    error: 'Copy error',
    noSupport: 'The browser does not support'
  },
  hexoVersion: '5.4.0'
} </script><meta name="generator" content="Hexo 5.4.0"><link rel="alternate" href="/atom.xml" title="一条鲤鱼" type="application/atom+xml">
</head><body><i class="fa fa-arrow-right" id="toggle-sidebar" aria-hidden="true"></i><div id="sidebar" data-display="true"><div class="toggle-sidebar-info text-center"><span data-toggle="Toggle article">Toggle site</span><hr></div><div class="sidebar-toc"><div class="sidebar-toc__title">Catalog</div><div class="sidebar-toc__progress"><span class="progress-notice">You've read</span><span class="progress-num">0</span><span class="progress-percentage">%</span><div class="sidebar-toc__progress-bar"></div></div><div class="sidebar-toc__content"><ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#Rc"><span class="toc-number">1.</span> <span class="toc-text">Rc</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Box-leak-%E6%9C%BA%E5%88%B6"><span class="toc-number">2.</span> <span class="toc-text">Box::leak()机制</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%8F%AF%E5%8F%98%E7%9A%84%E2%80%9CRc%E2%80%9D%E2%80%94%E2%80%94RefCell"><span class="toc-number">2.1.</span> <span class="toc-text">可变的“Rc”——RefCell</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%86%85%E9%83%A8%E5%8F%AF%E5%8F%98%E6%80%A7"><span class="toc-number">2.1.1.</span> <span class="toc-text">内部可变性</span></a></li></ol></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Arc%E5%92%8CMutex-RwLock"><span class="toc-number">3.</span> <span class="toc-text">Arc和Mutex&#x2F;RwLock</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%B0%8F%E7%BB%93"><span class="toc-number">4.</span> <span class="toc-text">小结</span></a></li></ol></div></div><div class="author-info hide"><div class="author-info__avatar text-center"><img src="/img/avatar.png"></div><div class="author-info__name text-center">LiYang</div><div class="author-info__description text-center"></div><hr><div class="author-info-articles"><a class="author-info-articles__archives article-meta" href="/archives"><span class="pull-left">Articles</span><span class="pull-right">13</span></a><a class="author-info-articles__tags article-meta" href="/tags"><span class="pull-left">Tags</span><span class="pull-right">6</span></a><a class="author-info-articles__categories article-meta" href="/categories"><span class="pull-left">Categories</span><span class="pull-right">7</span></a></div></div></div><div id="content-outer"><div class="no-bg" id="top-container"><div id="page-header"><span class="pull-left"> <a id="site-name" href="/">一条鲤鱼</a></span><i class="fa fa-bars toggle-menu pull-right" aria-hidden="true"></i><span class="pull-right menus">   <a class="site-page" href="/">Home</a><a class="site-page" href="/archives">Archives</a><a class="site-page" href="/tags">Tags</a><a class="site-page" href="/categories">Categories</a></span><span class="pull-right"></span></div><div id="post-info"><div id="post-title">Rust中的Rc、Arc和Box::leak()机制</div><div id="post-meta"><time class="post-meta__date"><i class="fa fa-calendar" aria-hidden="true"></i> 2021-09-15</time><span class="post-meta__separator">|</span><i class="fa fa-inbox post-meta__icon" aria-hidden="true"></i><a class="post-meta__categories" href="/categories/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/">编程语言</a></div></div></div><div class="layout" id="content-inner"><article id="post"><div class="article-container" id="post-content"><p>​    单一所有权规则，能满足我们大部分场景中分配和使用内存的需求，而且在编译时，通过 Rust 借用检查器就能完成静态检查，不会影响运行时效率。</p>
<p>​    但是，规则总会有例外，在日常工作中有些特殊情况该怎么处理呢？</p>
<ul>
<li>一个有向无环图（DAG）中，某个节点可能有两个以上的节点指向它，这个按照所有权模型怎么表述？</li>
<li>多个线程要访问同一块共享内存，怎么办？</li>
</ul>
<p>​    我们知道，这些问题在程序运行过程中才会遇到，在编译期，所有权的静态检查无法处理它们，所以为了更好的灵活性，Rust 提供了<strong>运行时的动态检查</strong>，来满足特殊场景下的需求。</p>
<p>​    这也是 Rust 处理很多问题的思路：<strong>编译时，处理大部分使用场景，保证安全性和效率；运行时，处理无法在编译时处理的场景，会牺牲一部分效率，提高灵活性。</strong></p>
<p>​    那么具体如何在运行时做动态检查呢？运行时的动态检查又如何与编译时的静态检查自洽呢？</p>
<p>​    Rust 的答案是使用引用计数的智能指针：<strong>Rc（Reference counter） 和 Arc（Atomic reference counter）</strong>。这里要特别说明一下，Arc 和 ObjC/Swift 里的 ARC（Automatic Reference Counting）不是一个意思，不过它们解决问题的手段类似，都是通过引用计数完成的。</p>
<h2 id="Rc"><a href="#Rc" class="headerlink" title="Rc"></a>Rc</h2><p>​    首先介绍Rc。对某个数据结构 T，我们可以创建引用计数 Rc，使其有多个所有者。Rc 会把对应的数据结构创建在堆上，堆是唯一可以让动态创建的数据被到处使用的内存。</p>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> std::rc::Rc;</span><br><span class="line"><span class="function"><span class="keyword">fn</span> <span class="title">main</span></span>() &#123;    </span><br><span class="line">  <span class="keyword">let</span> a = Rc::new(<span class="number">1</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>之后，如果想对数据创建更多的所有者，我们可以通过 <strong>clone()</strong> 来完成。</p>
<p><strong>对一个 Rc 结构进行 clone()，不会将其内部的数据复制，只会增加引用计数。</strong>而当一个 Rc 结构离开作用域被 drop() 时，也只会减少其引用计数，直到引用计数为零，才会真正清除对应的内存。</p>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> std::rc::Rc;</span><br><span class="line"><span class="function"><span class="keyword">fn</span> <span class="title">main</span></span>() &#123;</span><br><span class="line">    <span class="keyword">let</span> a = Rc::new(<span class="number">1</span>);</span><br><span class="line">    <span class="keyword">let</span> b = a.clone();</span><br><span class="line">    <span class="keyword">let</span> c = a.clone();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上面的代码我们创建了三个 Rc，分别是 a、b 和 c。它们共同指向堆上相同的数据，也就是说，堆上的数据有了三个共享的所有者。在这段代码结束时，c 先 drop，引用计数变成 2，然后 b drop、a drop，引用计数归零，堆上内存被释放。</p>
<p><img src="/2021/09/15/Rust%E4%B8%AD%E7%9A%84Rc%E5%92%8CBox-leak-%E6%9C%BA%E5%88%B6/a3510f9b565577bc74bc0dcda0b3e78c.png" alt="a3510f9b565577bc74bc0dcda0b3e78c"></p>
<p>你也许会有疑问：为什么我们生成了对同一块内存的多个所有者，但是，编译器不抱怨所有权冲突呢？</p>
<p>仔细看这段代码：首先 a 是 Rc::new(1) 的所有者，这毋庸置疑；然后 b 和 c 都调用了 a.clone()，分别得到了一个新的 Rc，所以从编译器的角度，abc 都各自拥有一个 Rc。如果文字你觉得稍微有点绕，可以看看 Rc 的 clone() 函数的实现，就很清楚了（<a target="_blank" rel="noopener" href="https://doc.rust-lang.org/src/alloc/rc.rs.html#1433-1453">源代码</a>）：</p>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fn</span> <span class="title">clone</span></span>(&amp;<span class="keyword">self</span>) -&gt; Rc&lt;T&gt; &#123;</span><br><span class="line">    <span class="comment">// 增加引用计数</span></span><br><span class="line">    <span class="keyword">self</span>.inner().inc_strong();</span><br><span class="line">    <span class="comment">// 通过 self.ptr 生成一个新的 Rc 结构</span></span><br><span class="line">    Self::from_inner(<span class="keyword">self</span>.ptr)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>所以，Rc 的 clone() 正如我们刚才说的，不复制实际的数据，只是一个引用计数的增加。你可能继续会疑惑：Rc 是怎么产生在堆上的？并且为什么这段堆内存不受栈内存生命周期的控制呢？</p>
<p>这就要说到Box:leak（）机制了。</p>
<h2 id="Box-leak-机制"><a href="#Box-leak-机制" class="headerlink" title="Box::leak()机制"></a>Box::leak()机制</h2><p>在所有权模型下，堆内存的生命周期，和创建它的栈内存的生命周期保持一致。所以 Rc 的实现似乎与此格格不入。的确，如果完全按照单一所有权模型，Rust 是无法处理 Rc 这样的引用计数的。</p>
<p>Rust 必须提供一种机制，让代码可以像 C/C++ 那样，<strong>创建不受栈内存控制的堆内存，从而绕过编译时的所有权规则。Rust 提供的方式是 Box::leak()。</strong></p>
<p>Box 是 Rust 下的智能指针，它可以强制把任何数据结构创建在堆上，然后在栈上放一个指针指向这个数据结构，但此时堆内存的生命周期仍然是受控的，跟栈上的指针一致。</p>
<p>而<strong>Box::leak()<strong>，顾名思义，它创建的对象，</strong>从堆内存上泄漏出去，不受栈内存控制，是一个自由的、生命周期可以大到和整个进程的生命周期一致的对象。</strong></p>
<p>下面这张图更容易理解：</p>
<p><img src="/2021/09/15/Rust%E4%B8%AD%E7%9A%84Rc%E5%92%8CBox-leak-%E6%9C%BA%E5%88%B6/9f1a17dea75f9cae596a56f51d007ccd.png" alt="9f1a17dea75f9cae596a56f51d007ccd"></p>
<p>Box::leak()相当于主动撕开了一个口子，允许内存泄漏。注意，<strong>在 C/C++ 下，其实你通过 malloc 分配的每一片堆内存，都类似 Rust 下的 Box::leak()。</strong> Rust 这样的设计符合最小权限原则（<a target="_blank" rel="noopener" href="https://en.wikipedia.org/wiki/Principle_of_least_privilege">Principle of least privilege</a>），可以最大程度帮助开发者撰写安全的代码。</p>
<p>有了 Box::leak()，我们就可以跳出 Rust 编译器的静态检查，保证 Rc 指向的堆内存，有最大的生命周期，然后我们再通过引用计数，在合适的时机，结束这段内存的生命周期。如果你对此感兴趣，可以看 <a target="_blank" rel="noopener" href="https://doc.rust-lang.org/src/alloc/rc.rs.html#342-350">Rc::new() 的源码</a>。</p>
<p>搞明白了 Rc，我们就进一步理解 Rust 是如何进行所有权的静态检查和动态检查了：</p>
<ul>
<li>静态检查，靠编译器保证代码符合所有权规则；</li>
<li>动态检查，通过 Box::leak 让堆内存拥有不受限的生命周期，然后在运行过程中，通过对引用计数的检查，保证这样的堆内存最终会得到释放。</li>
</ul>
<h3 id="可变的“Rc”——RefCell"><a href="#可变的“Rc”——RefCell" class="headerlink" title="可变的“Rc”——RefCell"></a>可变的“Rc”——RefCell</h3><p>Rc 是一个只读的引用计数器，你无法拿到 Rc 结构内部数据的可变引用，来修改这个数据。这就需要RefCell了。</p>
<p>和 Rc 类似，RefCell 也绕过了 Rust 编译器的静态检查，允许我们在运行时，对某个只读数据进行可变借用。这就涉及 Rust 另一个比较独特且有点难懂的概念：<a target="_blank" rel="noopener" href="https://doc.rust-lang.org/book/ch15-05-interior-mutability.html">内部可变性（interior mutability）</a>。</p>
<h4 id="内部可变性"><a href="#内部可变性" class="headerlink" title="内部可变性"></a>内部可变性</h4><p>首先，我们已经熟悉了外部可变性。当我们用 let mut 显式地声明一个可变的值，或者，用 &amp;mut 声明一个可变引用时，编译器可以在编译时进行严格地检查，保证只有可变的值或者可变的引用，才能修改值内部的数据，这被称作<strong>外部可变性（exterior mutability）</strong>，外部可变性通过 mut 关键字声明。</p>
<p>然而，这样不够灵活，有时候我们希望能够绕开这个编译时的检查，对并未声明成 mut 的值或者引用，也想进行修改。也就是说，<strong>在编译器的眼里，值是只读的，但是在运行时，这个值可以得到可变借用，从而修改内部的数据，这就是 RefCell 的用武之地。</strong></p>
<p>看一个简单的例子：</p>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> std::cell::RefCell;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">fn</span> <span class="title">main</span></span>() &#123;</span><br><span class="line">    <span class="keyword">let</span> data = RefCell::new(<span class="number">1</span>);</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">// 获得 RefCell 内部数据的可变借用</span></span><br><span class="line">        <span class="keyword">let</span> <span class="keyword">mut</span> v = data.borrow_mut();</span><br><span class="line">        *v += <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;data: &#123;:?&#125;&quot;</span>, data.borrow());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在这个例子里，data 是一个 RefCell，其初始值为 1。可以看到，我们并未将 data 声明为可变变量。之后我们可以通过使用 RefCell 的 <strong>borrow_mut() 方法</strong>，来<strong>获得一个可变的内部引用</strong>，然后对它做加 1 的操作。最后，我们可以通过 <strong>RefCell 的 borrow() 方法</strong>，<strong>获得一个不可变的内部引用</strong>，因为加了 1，此时它的值为 2。</p>
<p><strong>注意</strong>：这里为什么要把获取和操作可变借用的两句代码，用花括号分装到一个作用域下？</p>
<p>因为根据所有权规则，在同一个作用域下，<strong>我们不能同时有活跃的可变借用和不可变借用</strong>。通过这对花括号，我们明确地缩小了可变借用的生命周期，不至于和后续的不可变借用冲突。</p>
<p><strong>外部可变性和内部可变性的重要区别</strong>用下表来总结一下：</p>
<p><img src="/2021/09/15/Rust%E4%B8%AD%E7%9A%84Rc%E5%92%8CBox-leak-%E6%9C%BA%E5%88%B6/94bd27a93210ea829482663c9138de3c.png" alt="94bd27a93210ea829482663c9138de3c"></p>
<h2 id="Arc和Mutex-RwLock"><a href="#Arc和Mutex-RwLock" class="headerlink" title="Arc和Mutex/RwLock"></a>Arc和Mutex/RwLock</h2><p>我们可以用 Rc 和 RefCell 解决 DAG 的问题，那么，多个线程访问同一块内存的问题，是否也可以使用 Rc 来处理呢？</p>
<p>不行。因为 <strong>Rc 为了性能，使用的不是线程安全的引用计数器</strong>。因此，我们需要另一个引用计数的智能指针：<strong>Arc，它实现了线程安全的引用计数器。</strong></p>
<p>Arc 内部的引用计数使用了 <a target="_blank" rel="noopener" href="https://doc.rust-lang.org/src/alloc/sync.rs.html#303-312">Atomic Usize</a> ，而非普通的 usize。从名称上也可以感觉出来，Atomic Usize 是 usize 的原子类型，它使用了 CPU 的特殊指令，来保证多线程下的安全。如果你对原子类型感兴趣，可以看 <a target="_blank" rel="noopener" href="https://doc.rust-lang.org/std/sync/atomic/index.html">std::sync::atomic 的文档</a>。</p>
<p>Rust 实现两套不同的引用计数数据结构，完全是为了性能考虑，从这里我们也可以感受到 Rust 对性能的极致渴求。<strong>如果不用跨线程访问，可以用效率非常高的 Rc；如果要跨线程访问，那么必须用 Arc</strong>。</p>
<p>同样的，<strong>RefCell 也不是线程安全的</strong>，如果我们要在多线程中，使用内部可变性，Rust 提供了 <strong>Mutex 和 RwLock</strong>。</p>
<p><strong>Mutex 是互斥量</strong>，获得互斥量的线程对数据独占访问，<strong>RwLock 是读写锁</strong>，获得写锁的线程对数据独占访问，但当没有写锁的时候，允许有多个读锁。读写锁的规则和 Rust 的借用规则非常类似，我们可以类比着学。</p>
<p>Mutex 和 RwLock 都用在多线程环境下，对共享数据访问的保护上。多线程中需要把  Rc&lt;RefCell<T>&gt; 替换为  Arc&lt;Mutex<T>&gt; 或者  Arc&lt;RwLock<T>&gt;。</T></T></T></p>
<h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><ul>
<li><p>我们对所有权有了更深入的了解，掌握了 <strong>Rc / Arc、RefCell / Mutex / RwLock</strong> 这些数据结构的用法。</p>
</li>
<li><p>如果想绕过“一个值只有一个所有者”的限制，我们可以使用 <strong>Rc / Arc 这样带引用计数的智能指针</strong>。其中，Rc 效率很高，但只能使用在单线程环境下；Arc 使用了原子结构，效率略低，但可以安全使用在多线程环境下。</p>
</li>
<li><p>然而，Rc / Arc 是不可变的，如果想要修改内部的数据，需要引入<strong>内部可变性</strong>，在单线程环境下，可以在 Rc 内部使用 <strong>RefCell</strong>；在多线程环境下，可以使用 Arc 嵌套 <strong>Mutex</strong> 或者 <strong>RwLock</strong> 的方法。</p>
</li>
</ul>
<p><img src="/2021/09/15/Rust%E4%B8%AD%E7%9A%84Rc%E5%92%8CBox-leak-%E6%9C%BA%E5%88%B6/fc524d667fabeec0a8a22d0e10531086.png" alt="fc524d667fabeec0a8a22d0e10531086"></p>
</div></article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta">Author: </span><span class="post-copyright-info"><a href="mailto:undefined">LiYang</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta">Link: </span><span class="post-copyright-info"><a href="http://example.com/2021/09/15/Rust中的Rc和Box-leak-机制/">http://example.com/2021/09/15/Rust中的Rc和Box-leak-机制/</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta">Copyright Notice: </span><span class="post-copyright-info">All articles in this blog are licensed under <a target="_blank" rel="noopener" href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a> unless stating additionally.</span></div></div><div class="post-meta__tag-list"><a class="post-meta__tags" href="/tags/Rust/">Rust</a></div><nav id="pagination"><div class="prev-post pull-left"><a href="/2021/09/22/%E7%90%86%E8%A7%A3Rust%E7%9A%84%E5%BC%95%E7%94%A8%E5%92%8C%E5%80%9F%E7%94%A8/"><i class="fa fa-chevron-left">  </i><span>理解Rust的引用和借用</span></a></div><div class="next-post pull-right"><a href="/2021/09/14/learnMPI/"><span>MPI</span><i class="fa fa-chevron-right"></i></a></div></nav></div></div><footer><div class="layout" id="footer"><div class="copyright">&copy;2013 - 2022 By LiYang</div><div class="framework-info"><span>Driven - </span><a target="_blank" rel="noopener" href="http://hexo.io"><span>Hexo</span></a><span class="footer-separator">|</span><span>Theme - </span><a target="_blank" rel="noopener" href="https://github.com/Molunerfinn/hexo-theme-melody"><span>Melody</span></a></div><div class="busuanzi"><script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script><span id="busuanzi_container_page_pv"><i class="fa fa-file"></i><span id="busuanzi_value_page_pv"></span><span></span></span></div></div></footer><i class="fa fa-arrow-up" id="go-up" aria-hidden="true"></i><script src="https://cdn.jsdelivr.net/npm/animejs@latest/anime.min.js"></script><script src="https://cdn.jsdelivr.net/npm/jquery@latest/dist/jquery.min.js"></script><script src="https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@latest/dist/jquery.fancybox.min.js"></script><script src="https://cdn.jsdelivr.net/npm/velocity-animate@latest/velocity.min.js"></script><script src="https://cdn.jsdelivr.net/npm/velocity-ui-pack@latest/velocity.ui.min.js"></script><script src="/js/utils.js?version=1.9.0"></script><script src="/js/fancybox.js?version=1.9.0"></script><script src="/js/sidebar.js?version=1.9.0"></script><script src="/js/copy.js?version=1.9.0"></script><script src="/js/fireworks.js?version=1.9.0"></script><script src="/js/transition.js?version=1.9.0"></script><script src="/js/scroll.js?version=1.9.0"></script><script src="/js/head.js?version=1.9.0"></script><script>if(/Android|webOS|iPhone|iPod|iPad|BlackBerry/i.test(navigator.userAgent)) {
  $('#nav').addClass('is-mobile')
  $('footer').addClass('is-mobile')
  $('#top-container').addClass('is-mobile')
}</script></body></html>